package cn.yogehaoren.badfox.core;

import cn.yogehaoren.badfox.annotation.DownloadInterceptor;
import cn.yogehaoren.badfox.annotation.Interceptor;
import cn.yogehaoren.badfox.annotation.Queue;
import cn.yogehaoren.badfox.def.DefaultLocalQueue;
import cn.yogehaoren.badfox.def.DefaultLocalMixQueue;
import cn.yogehaoren.badfox.exception.IgnoreRequestException;
import cn.yogehaoren.badfox.http.okhttp.OkHttpDownloader;
import cn.yogehaoren.badfox.spring.common.BadFoxCache;
import cn.yogehaoren.badfox.struct.*;
import cn.yogehaoren.badfox.utils.StructValidator;
import cn.yogehaoren.badfox.def.BaseBadFox;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 */
public class BadFoxProcessor implements Runnable {


    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final Pattern metaRefresh = Pattern.compile("<(?:META|meta|Meta)\\s+(?:HTTP-EQUIV|http-equiv)\\s*=\\s*\"refresh\".*(?:url|URL)=(\\S*)\".*/?>");

    public BadFoxProcessor() {

    }

    @Override
    public void run() {
        while (true) {
            Request request = null;
            CrawlerModel realCrawlerModel = null;
            for(CrawlerModel crawlerModel: BadFoxCache.getCrawlerModelContext().values()){
                    BaseBadFox crawler = crawlerModel.getInstance();
                    BadFoxQueue queue = crawlerModel.getQueueInstance();
                    try {
                        Queue queueAnnotation = queue.getClass().getAnnotation(Queue.class);
                        if(queueAnnotation!= null && queueAnnotation.mix()){

                            // 混合性能增强模式
                            request = queue.bPop("");
                            realCrawlerModel = BadFoxCache.getCrawlerModelContext().get(request.getCrawlerName());
                            crawler = realCrawlerModel.getInstance();
                            queue = realCrawlerModel.getQueueInstance();

                        }else {
                            request = queue.bPop(crawlerModel.getCrawlerName(), 5, TimeUnit.SECONDS);
                            realCrawlerModel = crawlerModel;
                        }

                        if (request == null) {
                            continue;
                        }


                        if (request.isStop()) {
                            logger.info("BadFoxProcessor[{}] will stop!", Thread.currentThread().getName());
                            break;
                        }
                        //对请求开始校验
                        if (!StructValidator.validateAnno(request)) {
                            logger.warn("Request={} is illegal", JSON.toJSONString(request));
                            continue;
                        }
                        if (!StructValidator.validateAllowRules(crawler.allowRules(), request.getUrl())) {
                            logger.warn("Request={} will be dropped by allowRules=[{}]", JSON.toJSONString(request), StringUtils.join(crawler.allowRules(), ","));
                            continue;
                        }
                        if (StructValidator.validateDenyRules(crawler.denyRules(), request.getUrl())) {
                            logger.warn("Request={} will be dropped by denyRules=[{}]", JSON.toJSONString(request), StringUtils.join(crawler.denyRules(), ","));
                            continue;
                        }
                        //异常请求重试次数超过最大重试次数三次后，直接放弃处理
                        if (request.getCurrentReqCount() >= request.getMaxReqCount()+3) {
                            continue;
                        }

                        BadFoxDownloader downloader = new OkHttpDownloader(realCrawlerModel);
                        Method requestCallback = realCrawlerModel.getMemberMethods().get(request.getCallBack());

                        boolean throwRequest = false;
                        Response foxResponse = null;
                        for (BadFoxDownloadInterceptor interceptor : getDownloadInterceptors()) {
                            DownloadInterceptor interAnno = interceptor.getClass().getAnnotation(DownloadInterceptor.class);
                            if (interAnno.everyMethod() || requestCallback.isAnnotationPresent(interceptor.getTargetAnnotationClass()) || realCrawlerModel.getClazz().isAnnotationPresent(interceptor.getTargetAnnotationClass())) {

                                try{
                                    CommonObject result = interceptor.before(request, foxResponse);
                                    if(result instanceof Request){
                                        request = (Request) result;
                                    }else if(result instanceof Response){
                                        foxResponse = (Response) result;
                                    }

                                }catch (IgnoreRequestException e){
                                    throwRequest = true;
                                    break;
                                }
                            }
                        }

                        if(throwRequest){
                            continue;
                        }

                        if(foxResponse == null){
                            foxResponse = downloader.process(request);
                        }

                        throwRequest = false;

                        for (BadFoxDownloadInterceptor interceptor : getDownloadInterceptors()) {
                            DownloadInterceptor interAnno = interceptor.getClass().getAnnotation(DownloadInterceptor.class);
                            if (interAnno.everyMethod() || requestCallback.isAnnotationPresent(interceptor.getTargetAnnotationClass()) || realCrawlerModel.getClazz().isAnnotationPresent(interceptor.getTargetAnnotationClass())) {

                                try{
                                    CommonObject result = interceptor.after(request, foxResponse);
                                    if(result instanceof Response){
                                        foxResponse = (Response) result;
                                    }

                                }catch (IgnoreRequestException e){
                                    throwRequest = true;
                                    break;
                                }
                            }
                        }

                        if(throwRequest){
                            continue;
                        }

                        if (StringUtils.isNotBlank(foxResponse.getContent()) && BodyType.TEXT.equals(foxResponse.getBodyType())) {
                            Matcher mm = metaRefresh.matcher(foxResponse.getContent());
                            int refreshCount = 0;
                            while ( mm.find() && refreshCount < 3) {
                                String nextUrl = mm.group(1).replaceAll("'", "");
                                foxResponse = downloader.metaRefresh(nextUrl);
                                mm = metaRefresh.matcher(foxResponse.getContent());
                                refreshCount += 1;
                            }
                        }
                        BadFoxCache.setResponseContext(foxResponse);
                        //处理回调函数
                        if (!request.isLambdaCb()){
                            doCallback(request, foxResponse, realCrawlerModel);
                        }else {
                            doLambdaCallback(request, foxResponse, realCrawlerModel);
                        }
                        logger.debug("Crawler[{}] ,url={} ,responseStatus={}", realCrawlerModel.getCrawlerName(), request.getUrl(), downloader.statusCode());
                    } catch (Throwable e) {
                        logger.error(e.getMessage(), e);
                        if (request == null) {
                            continue;
                        }
                        if (request.getCurrentReqCount() < request.getMaxReqCount() && queue != null) {
                            request.incrReqCount();
                            queue.push(request);
                            logger.info("Request process error,req will go into queue again,url={},maxReqCount={},currentReqCount={}", request.getUrl(), request.getMaxReqCount(), request.getCurrentReqCount());
                        } else if (request.getCurrentReqCount() >= request.getMaxReqCount() && crawler!=null && request.getMaxReqCount() > 0) {
                            crawler.handleErrorRequest(request);
                        }
                    }
                    BadFoxCache.clearResponseContext();


                }
            }

    }

    private void doCallback(Request request, Response response, CrawlerModel crawlerModel) throws Exception {

        Method requestCallback = crawlerModel.getMemberMethods().get(request.getCallBack());
        if (requestCallback == null) {
            logger.info("can not find callback function");
            return;
        }
        for (BadFoxInterceptor interceptor : getInterceptors()) {
            Interceptor interAnno = interceptor.getClass().getAnnotation(Interceptor.class);
            if (interAnno.everyMethod() || requestCallback.isAnnotationPresent(interceptor.getTargetAnnotationClass()) || crawlerModel.getClazz().isAnnotationPresent(interceptor.getTargetAnnotationClass())) {
                interceptor.before(requestCallback, response);
            }
        }
        if(request.getDelay()>0){
            TimeUnit.SECONDS.sleep(request.getDelay());
        }else if (crawlerModel.getDelay() > 0) {
            TimeUnit.SECONDS.sleep(crawlerModel.getDelay());
        }
        requestCallback.invoke(crawlerModel.getInstance(), response);

        for (BadFoxInterceptor interceptor : getInterceptors()) {
            Interceptor interAnno = interceptor.getClass().getAnnotation(Interceptor.class);
            if (interAnno.everyMethod() || requestCallback.isAnnotationPresent(interceptor.getTargetAnnotationClass()) || crawlerModel.getClazz().isAnnotationPresent(interceptor.getTargetAnnotationClass())) {
                interceptor.after(requestCallback, response);
            }
        }
    }

    private void doLambdaCallback(Request request, Response response, CrawlerModel crawlerModel) throws Exception {
        Request.BadFoxCallbackFunc<BadFox,Response> requestCallback = request.getCallBackFunc();
        if (requestCallback == null) {
            logger.info("can not find callback function");
            return;
        }
        for (BadFoxInterceptor interceptor : getInterceptors()) {
            Interceptor interAnno = interceptor.getClass().getAnnotation(Interceptor.class);
            if (interAnno.everyMethod()) {
                interceptor.before(null, response);
            }
        }

        if (crawlerModel.getDelay() > 0) {
            TimeUnit.SECONDS.sleep(crawlerModel.getDelay());
        }

        requestCallback.call(crawlerModel.getInstance(),response);
        for (BadFoxInterceptor interceptor : getInterceptors()) {
            Interceptor interAnno = interceptor.getClass().getAnnotation(Interceptor.class);
            if (interAnno.everyMethod() ) {
                interceptor.after(null, response);
            }
        }
    }

    private List<BadFoxInterceptor> getInterceptors(){
        return BadFoxCache.getInterceptors();
    }

    public List<BadFoxDownloadInterceptor> getDownloadInterceptors() {
        return BadFoxCache.getDownloadInterceptors();
    }
}
